package com.cmc.cloud.cmclink.doc.service.impl.si;

import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.stream.Collectors;

import javax.annotation.Resource;

import org.apache.commons.collections.CollectionUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;

import com.alibaba.cloud.commons.lang.StringUtils;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.cmc.cloud.cmclink.doc.api.document.emums.*;
import com.cmc.cloud.cmclink.doc.constants.BlnoFreightConstants;
import com.cmc.cloud.cmclink.doc.constants.CommonConstants;
import com.cmc.cloud.cmclink.doc.constants.RedisKeyConstants;
import com.cmc.cloud.cmclink.doc.constants.errorconstant.BlnoFreightErrorCodeConstants;
import com.cmc.cloud.cmclink.doc.constants.errorconstant.BlnoLocalCostImErrorCodeConstants;
import com.cmc.cloud.cmclink.doc.constants.errorconstant.LocalCostRateErrorCodeConstants;
import com.cmc.cloud.cmclink.doc.convert.BlnoCntrConvert;
import com.cmc.cloud.cmclink.doc.convert.BlnoFreightConvert;
import com.cmc.cloud.cmclink.doc.convert.BlnoLocalCostImConvert;
import com.cmc.cloud.cmclink.doc.entity.*;
import com.cmc.cloud.cmclink.doc.mapper.*;
import com.cmc.cloud.cmclink.doc.rpcservice.AccountTransfer;
import com.cmc.cloud.cmclink.doc.rpcservice.CustomerRpcService;
import com.cmc.cloud.cmclink.doc.service.BlnoCntrService;
import com.cmc.cloud.cmclink.doc.service.si.*;
import com.cmc.cloud.cmclink.doc.util.CostChargeUtil;
import com.cmc.cloud.cmclink.doc.util.RedisUtils;
import com.cmc.cloud.cmclink.doc.util.VslvoyUtil;
import com.cmc.cloud.cmclink.doc.util.sepcial.CommonUtil;
import com.cmc.cloud.cmclink.doc.vo.blnocntrvo.BlnoCntrRespVO;
import com.cmc.cloud.cmclink.doc.vo.blnofreightvo.BlnoFreightCalReqVO;
import com.cmc.cloud.cmclink.doc.vo.blnolocalcostvo.*;
import com.cmc.cloud.cmclink.doc.vo.calculatelogvo.CalculateOperateLogCreateReqVO;
import com.cmc.cloud.cmclink.fin.api.base.dto.req.DocBlnoBaseReqDTO;
import com.cmc.cloud.cmclink.fin.api.base.dto.req.DocBlnoCreateReqDTO;
import com.cmc.cloud.cmclink.fin.api.base.dto.req.DocBlnoFreightInfoReqDTO;
import com.cmc.cloud.cmclink.fin.api.base.dto.req.DocBlnoVslvoyReqDTO;
import com.cmc.cloud.cmclink.svl.api.base.resp.VesselVoyRespDTO;
import com.cmes.framework.common.exception.util.ServiceExceptionUtil;
import com.cmes.framework.ex.Assertion;
import com.cmes.framework.mybatis.core.query.LambdaQueryWrapperX;
import com.cmes.framework.security.core.util.SecurityFrameworkUtils;
import com.google.gson.Gson;

import lombok.extern.slf4j.Slf4j;

/**
 * 进口提单本地费用 Service 实现类
 *
 * @author 招商轮船-管理员
 */
@Service
@Validated
@Slf4j
public class BlnoLocalCostImServiceImpl implements BlnoLocalCostImService {
    private static final String CNTR_KEY_SPILT = "_";

    /**
     * 宁波港港口代码
     */
    private static final String PORT_CODE_CNNGB = "CNNGB";

    private static final String SPILT_RATE_UNIT = ",";

    @Resource
    private BlnoService blnoService;
    @Resource
    private BlnoCntrService blnoCntrService;
    @Resource
    private LocalCostRateMapper localCostRateMapper;
    @Resource
    private BlnoCargoService blnoCargoService;
    @Resource
    private BlnoVslvoyService blnoVslvoyService;
    @Resource
    private BlnoLocalCostMapper blnoLocalCostMapper;
    @Resource
    private BlnoLocalCostDetailMapper blnoLocalCostDetailMapper;
    @Resource
    private CalculateOperateLogService calLogService;
    @Resource
    private AccountTransfer accountTransfer;
    @Resource
    private BlnoLocalChargeMapper localChargeMapper;
    @Resource
    private CustomerRpcService nameTransferService;

    @Resource
    private BlnoLocalCostMapper localCostMapper;

    @Resource
    private RedisUtils redisUtils;

    @Value("${local.cost.redisTimeOut}")
    private Long redisTimeOut;

    @Resource
    private BlnoMapper blnoMapper;

    @Resource
    private CostChargeUtil costChargeUtil;

    @Override
    public List<BlnoLocalCostImRespVO> getBlnoWithLocalCostList(BlnoFreightCalReqVO reqVo) {
        checkParamValid(reqVo);

        List<String> rejectStatuList = new ArrayList<>();
        rejectStatuList.add(SiStatusEnum.BookingDraft.getValue());
        rejectStatuList.add(SiStatusEnum.BookingProcessing.getValue());
        reqVo.setBlNoStatus(rejectStatuList);
        List<BlnoLocalCostImRespVO> rspList = localCostMapper.getBlnoWithLocalCostImList(reqVo);
        if (CollectionUtils.isEmpty(rspList)) {
            return Collections.emptyList();
        }

        // 获取供应商信息
        Set<String> codes = rspList.stream().map(BlnoLocalCostImRespVO::getBkpCode).filter(Objects::nonNull)
            .filter(name -> !name.isEmpty()).collect(Collectors.toSet());
        Map<String, String> companyNameMap = nameTransferService.getCompanyNameMap(new ArrayList<>(codes));

        rspList.forEach(resp -> {
            resp.setToAccountFlag(resp.getToAccount() != null && resp.getToAccount() != 0);
            resp.setBkpNameEn(companyNameMap.getOrDefault(resp.getBkpCode(), null));
        });
        return rspList;
    }

    private void checkParamValid(BlnoFreightCalReqVO reqVo) {
        String vesselCode = reqVo.getBkgVesselCode();
        String voy = reqVo.getBkgVoyage();
        String blno = reqVo.getBlNo();
        boolean check = StringUtils.isEmpty(blno) && (StringUtils.isEmpty(voy) || StringUtils.isEmpty(vesselCode));
        if (check) {
            throw ServiceExceptionUtil.exception(BlnoFreightErrorCodeConstants.FREIGHT_PARAM_VALID_FAILED);
        }
    }

    @Override
    public Map<Long, String> calLocalCostIm(Collection<Long> ids, String transferCode, String exImType,
        String currentPort) {
        if (CollectionUtils.isEmpty(ids)) {
            return new HashMap<>(16);
        }
        Map<Long, String> errMsgMap = new HashMap<>(16);
        // 过滤无效id
        List<BlnoDO> blnoDoList = getBlnoCheckIds(ids, errMsgMap);
        if (CollectionUtils.isEmpty(blnoDoList)) {
            return errMsgMap;
        }
        ids = blnoDoList.stream().map(BlnoDO::getId).collect(Collectors.toSet());

        // 获取cntr, cargo, rate, vslvoy等基础数据
        List<BlnoCntrDO> cntrDOList = blnoCntrService.getBlnoCntrByBlnoIds(ids);
        List<BlnoCargoDO> blnoCargoDOList = blnoCargoService.getBlnoCargoByBlnoIds(ids);
        List<BlnoVslvoyDO> blnoVslvoyDOList = blnoVslvoyService.getBlnoVslvoyBySiIdList(ids);
        Map<Long, List<BlnoCntrDO>> cntrListMap =
            cntrDOList.stream().collect(Collectors.groupingBy(BlnoCntrDO::getBlnoId));
        Map<Long, List<BlnoCargoDO>> cargoListMap =
            blnoCargoDOList.stream().collect(Collectors.groupingBy(BlnoCargoDO::getBlnoId));
        Map<Long, List<BlnoVslvoyDO>> vslvoyListMap =
            blnoVslvoyDOList.stream().collect(Collectors.groupingBy(BlnoVslvoyDO::getBlnoId));

        // 查询rate表
        List<LocalCostRateDO> rateUnitList = getLocalCostRateList(exImType, currentPort);
        rateUnitList = filterRateByTransferCode(transferCode, rateUnitList);
        if (CollectionUtils.isEmpty(rateUnitList)) {
            Assertion.assertTrue(false).raise0(BlnoFreightErrorCodeConstants.LOCAL_COST_RATE_NULL.getCode(), exImType);
        }
        // 根据费率类型分类，CNTR费率类型需要排序，BILL类型不需排序
        List<LocalCostRateDO> rateCntrUnitList = getSortCntrLocalCostRate(rateUnitList);
        List<LocalCostRateDO> rateBillUnitList = rateUnitList.stream()
            .filter(rate -> rate.getPer().equals(CalculatePerEnum.BILL.getValue())).collect(Collectors.toList());

        // redis加锁
        Set<Long> lockIds = new HashSet<>();
        for (Long blnoId : ids) {
            if (redisUtils.checKeyExist(RedisKeyConstants.LOCAL_COST_KEY + blnoId)) {
                lockIds.add(blnoId);
                errMsgMap.put(blnoId, BlnoFreightConstants.REDIS_KEY_LOCKED);
            } else {
                redisUtils.setStringTimeOutByMinute(RedisKeyConstants.LOCAL_COST_KEY + blnoId, String.valueOf(blnoId),
                    redisTimeOut);
                errMsgMap.put(blnoId, CommonConstants.RESPONSE_SUCCESS);
            }
        }
        if (CollectionUtils.isNotEmpty(lockIds)) {
            ids.removeAll(lockIds);
            blnoDoList =
                blnoDoList.stream().filter(blno -> !lockIds.contains(blno.getId())).collect(Collectors.toList());
        }
        if (CollectionUtils.isEmpty(ids)) {
            log.info(" all ids is lock");
            return errMsgMap;
        }

        List<BlnoLocalCostDO> blnoLocalCostList = new ArrayList<>();
        List<BlnoLocalCostDetailDO> blnoLocalCostDetailList = new ArrayList<>();

        try {
            for (BlnoDO blno : blnoDoList) {
                // 检查DO状态，不为空则不计算
                if (!checkDo(blno)) {
                    continue;
                }

                List<BlnoVslvoyDO> vslvoyList = vslvoyListMap.getOrDefault(blno.getId(), new ArrayList<>());

                // 获取Ptu时间
                VesselVoyRespDTO respDto = costChargeUtil.getVoyQueryRespDTO(currentPort, exImType, vslvoyList);
                String terminalCode = respDto.getTerminalCode();
                String ptu = respDto.getPtu();
                DateTimeFormatter formatter = DateTimeFormatter.ofPattern(CommonConstants.PTU_PATTERN);
                LocalDate etuLocaleDate = LocalDate.parse(ptu, formatter);

                String depotCode = getDepotCode(vslvoyList, exImType, currentPort, transferCode, terminalCode);
                String vesselCode = getVesselCode(vslvoyList, exImType, currentPort);

                List<BlnoLocalCostDO> localHostLogList = new ArrayList<>();
                String calculateType;
                if (ExImTypeEnum.IM.getValue().equals(exImType)) {
                    calculateType = CalculateTypeEnum.CALCULATE_LOCAL_COST_IM.getValue();
                } else {
                    calculateType = CalculateTypeEnum.CALCULATE_LOCAL_COST_EX.getValue();
                }
                if (CollectionUtils.isEmpty(cargoListMap.getOrDefault(blno.getId(), null))) {
                    log.error("blno cargo not exist, blnoId : {}", blno.getId());
                    throw ServiceExceptionUtil.exception(BlnoFreightErrorCodeConstants.BLNO_CARGO_NOT_EXISTS);
                }
                BlnoCargoDO blnoCargoDO = cargoListMap.get(blno.getId()).get(0);

                // 合并cntr，用于计算数量
                List<BlnoCntrDO> cntrList = cntrListMap.getOrDefault(blno.getId(), new ArrayList<>());
                Map<String, Long> cntrCountMap =
                    cntrList.stream().collect(Collectors.groupingBy(this::buildCntrKey, Collectors.counting()));

                // 获取箱型匹配的rate, Map<size+type+owner, Set<费率Id>>
                Map<String, Set<Long>> cntrMatchRatesMap = matchCntrAndRate(blno, vslvoyList, rateCntrUnitList,
                    blnoCargoDO, depotCode, vesselCode, cntrCountMap, etuLocaleDate, exImType);

                // 处理按箱计算
                for (LocalCostRateDO rateUnit : rateCntrUnitList) {
                    Set<String> existKey = new HashSet<>();
                    // 用于判断是否已经添加到localcost主表
                    for (BlnoCntrDO cntr : cntrList) {
                        String key = buildCntrKey(cntr);
                        // 不匹配
                        if (!cntrMatchRatesMap.containsKey(key)
                            || !cntrMatchRatesMap.get(key).contains(rateUnit.getId())) {
                            continue;
                        }
                        BlnoLocalCostDetailDO blnoLocalCostDetaiTmpl =
                            buildLocalCostDetail(rateUnit, blno, blnoCargoDO, cntr, currentPort, exImType);
                        blnoLocalCostDetailList.add(blnoLocalCostDetaiTmpl);
                        if (!existKey.contains(key)) {
                            existKey.add(key);
                            long count = cntrCountMap.get(key);
                            BlnoLocalCostDO blnoLocalCostTmp = buildLocalCost(blnoLocalCostDetaiTmpl, count);
                            blnoLocalCostList.add(blnoLocalCostTmp);
                            localHostLogList.add(blnoLocalCostTmp);
                        }
                    }
                }

                // 按票计算
                for (LocalCostRateDO rateUnit : rateBillUnitList) {
                    if (!rateMatchBlnoWithBill(blno, vslvoyList, rateUnit, blnoCargoDO, depotCode, etuLocaleDate,
                        exImType)) {
                        continue;
                    }
                    BlnoLocalCostDetailDO blnoLocalCostDetaiTmpl =
                        buildLocalCostDetail(rateUnit, blno, blnoCargoDO, null, currentPort, exImType);
                    blnoLocalCostDetailList.add(blnoLocalCostDetaiTmpl);

                    BlnoLocalCostDO blnoLocalCostTmp = buildLocalCost(blnoLocalCostDetaiTmpl, 1L);
                    blnoLocalCostList.add(blnoLocalCostTmp);
                    localHostLogList.add(blnoLocalCostTmp);
                }

                // 计费操作保存日志
                saveCalLog(blno, rateUnitList, localHostLogList, cntrList, calculateType,
                    CalculateStatusEnum.SUCCESS.getValue(), null);
            }

            if (blnoLocalCostList.isEmpty()) {
                throw ServiceExceptionUtil.exception(LocalCostRateErrorCodeConstants.LOCAL_COST_RATE_NOT_EXISTS);
            }
            // 保存数据表
            try {
                saveLocalCostDb(ids, blnoLocalCostList, blnoLocalCostDetailList, exImType);
            } catch (Exception e) {
                log.error("caught an exception", e);
                Assertion.assertTrue(false)
                    .raise0(BlnoLocalCostImErrorCodeConstants.LOCQAL_COST_IM_SAVE_DB_FAILED.getCode(), e.toString());
            }
        } finally {
            for (Long blnoId : ids) {
                redisUtils.deleteString(RedisKeyConstants.LOCAL_COST_KEY + blnoId);
            }
        }

        return errMsgMap;
    }

    private List<LocalCostRateDO> filterRateByTransferCode(String transferCode, List<LocalCostRateDO> rateUnitList) {
        if (StringUtils.isEmpty(transferCode)) {
            return rateUnitList;
        }
        rateUnitList = rateUnitList.stream().filter(rate -> checkDeoptContain(rate.getDepotCodes(), transferCode))
            .collect(Collectors.toList());
        return rateUnitList;
    }

    private boolean checkDeoptContain(String depotCodes, String transferCode) {
        if (StringUtils.isEmpty(depotCodes)) {
            return false;
        }

        String[] parts = depotCodes.split(SPILT_RATE_UNIT);
        for (String part : parts) {
            if (transferCode.equals(part)) {
                return true;
            }
        }

        return false;
    }

    private List<BlnoDO> getBlnoCheckIds(Collection<Long> ids, Map<Long, String> errMsgMap) {
        List<String> rejectStatuList = new ArrayList<>();
        rejectStatuList.add(SiStatusEnum.BookingDraft.getValue());
        rejectStatuList.add(SiStatusEnum.BookingProcessing.getValue());
        LambdaQueryWrapperX<BlnoDO> queryWrapperX = new LambdaQueryWrapperX<>();
        queryWrapperX.in(BlnoDO::getId, ids).eq(BlnoDO::getValid, SiValidEnum.VALID.getValue())
            .notIn(BlnoDO::getSiStatus, rejectStatuList).eq(BlnoDO::getPodBlFlag, true)
            .and(wrap -> wrap.isNull(BlnoDO::getDoStatus).or().eq(BlnoDO::getDoStatus, ""));
        List<BlnoDO> blnoDoList = blnoMapper.selectList(queryWrapperX);
        Set<Long> idDbs = blnoDoList.stream().map(BlnoDO::getId).collect(Collectors.toSet());
        ids.forEach(id -> {
            if (!idDbs.contains(id)) {
                errMsgMap.put(id, BlnoFreightConstants.INVALID_ID);
            }
        });

        return blnoDoList;
    }

    private boolean checkDo(BlnoDO blnoDO) {
        return org.apache.tika.utils.StringUtils.isEmpty(blnoDO.getDoStatus());
    }

    private BlnoLocalCostDetailDO buildLocalCostDetail(LocalCostRateDO rateUnit, BlnoDO blnoDO, BlnoCargoDO blnoCargoDO,
        BlnoCntrDO cntr, String localPort, String exImType) {
        BlnoLocalCostDetailDO localCost = new BlnoLocalCostDetailDO();
        localCost.setBlnoId(blnoDO.getId());
        localCost.setVendor(rateUnit.getVendor());
        localCost.setImExType(exImType);
        localCost.setChargeCode(rateUnit.getChargeCode());
        localCost.setPer(rateUnit.getPer());
        localCost.setCargoType(blnoCargoDO.getCargoType());
        localCost.setRate(rateUnit.getRate());
        localCost.setCurrency(rateUnit.getCurrency());
        if (ExImTypeEnum.IM.getValue().equals(exImType)) {
            localCost.setPayment(PaymentEnum.C.getValue());
        } else {
            localCost.setPayment(PaymentEnum.P.getValue());
        }
        localCost.setRecordType(RecordTypeEnum.AUTO.getValue());
        localCost.setLocalPort(localPort);

        if (CalculatePerEnum.CNTR.getValue().equals(rateUnit.getPer())) {
            localCost.setContainerNo(cntr.getCntrNo());
            localCost.setCntrSize(cntr.getCntrSize());
            localCost.setCntrType(cntr.getCntrType());
        }
        localCost.setQuantity(new BigDecimal(1));
        BigDecimal amount =
            CommonUtil.decimalMulHalfUp(rateUnit.getRate(), localCost.getQuantity(), localCost.getCurrency());
        localCost.setAmount(amount);
        return localCost;
    }

    private BlnoLocalCostDO buildLocalCost(BlnoLocalCostDetailDO localCostDetail, Long count) {
        BlnoLocalCostDO localCost = BlnoLocalCostImConvert.INSTANCE.convertLocalCostDO(localCostDetail);
        localCost.setToAccount(false);
        localCost.setQuantity(new BigDecimal(count));
        BigDecimal amount =
            CommonUtil.decimalMulHalfUp(localCost.getRate(), localCost.getQuantity(), localCost.getCurrency());
        localCost.setAmount(amount);
        return localCost;
    }

    private Map<String, Set<Long>> matchCntrAndRate(BlnoDO blno, List<BlnoVslvoyDO> vslvoyList,
        List<LocalCostRateDO> rateSortList, BlnoCargoDO blnoCargoDO, String depotCode, String vesselCode,
        Map<String, Long> cntrCountMap, LocalDate etuLocaleDate, String exImType) {
        Map<String, Set<Long>> cntrMatchRateSetMap = new HashMap<>(16);
        for (String key : cntrCountMap.keySet()) {
            String preCharge = "";
            Set<Long> rateIdSet = new HashSet<>();
            for (LocalCostRateDO rateSort : rateSortList) {
                if (!rateMatchBlnoWithCntr(blno, vslvoyList, rateSort, blnoCargoDO, depotCode, key, vesselCode,
                    etuLocaleDate, exImType)) {
                    continue;
                }
                if (rateSort.getChargeCode().equals(preCharge)) {
                    continue;
                }
                preCharge = rateSort.getChargeCode();
                rateIdSet.add(rateSort.getId());
            }
            cntrMatchRateSetMap.put(key, rateIdSet);
        }
        return cntrMatchRateSetMap;
    }

    private String buildCntrKey(BlnoCntrDO cntrDO) {
        return cntrDO.getCntrSize() + CNTR_KEY_SPILT + cntrDO.getCntrType() + CNTR_KEY_SPILT + cntrDO.getCntrOwner();
    }

    private boolean rateMatchBlnoWithCntr(BlnoDO blno, List<BlnoVslvoyDO> vslvoyList, LocalCostRateDO rateUnit,
        BlnoCargoDO blnoCargoDO, String depotCode, String cntrKey, String vesselCode, LocalDate etuLocaleDate,
        String exImType) {
        // check depotCode
        if (!checkItemsMatch(rateUnit.getDepotCodes(), depotCode)) {
            return false;
        }

        // check cntr_size
        if (!checkItemMatch(rateUnit.getCntrSize(), cntrKey.split(CNTR_KEY_SPILT)[0])) {
            return false;
        }

        // check cntr_type
        if (!checkItemMatch(rateUnit.getCntrType(), cntrKey.split(CNTR_KEY_SPILT)[1])) {
            return false;
        }

        // check cntr_status
        if (!checkItemMatch(rateUnit.getCntrStatus(), blno.getCntrStatus())) {
            return false;
        }

        // check cargo_type
        if (!checkItemMatch(rateUnit.getCargoType(), blnoCargoDO.getCargoType())) {
            return false;
        }

        // check cntr_owner
        if (!checkItemMatch(rateUnit.getCntrOwner(), cntrKey.split(CNTR_KEY_SPILT)[CommonConstants.TWO])) {
            return false;
        }

        // check bkp_codes
        if (!checkItemsMatch(rateUnit.getBkpCodes(), blno.getBkpCode())) {
            return false;
        }

        // check vessel_code
        if (!checkItemMatch(rateUnit.getVesselCode(), vesselCode)) {
            return false;
        }

        // check lane_code
        if (!checkLaneCode(vslvoyList, rateUnit, exImType)) {
            return false;
        }

        if (!checkItemMatch(rateUnit.getRouteId(), blno.getRouteId())) {
            return false;
        }

        // check begin_date to end_date
        return checkValidDate(rateUnit.getBeginDate(), rateUnit.getEndDate(), etuLocaleDate);
    }

    private boolean rateMatchBlnoWithBill(BlnoDO blno, List<BlnoVslvoyDO> vslvoyList, LocalCostRateDO rateUnit,
        BlnoCargoDO blnoCargoDO, String depotCode, LocalDate etuLocaleDate, String exImType) {
        // check depotCode
        if (!checkItemsMatch(rateUnit.getDepotCodes(), depotCode)) {
            return false;
        }

        // check cargo_type
        if (!checkItemMatch(rateUnit.getCargoType(), blnoCargoDO.getCargoType())) {
            return false;
        }

        // check cntr_owner
        if (!checkItemMatch(rateUnit.getCntrOwner(), blno.getCntrOwner())) {
            return false;
        }

        // check bkp_codes
        if (!checkItemsMatch(rateUnit.getBkpCodes(), blno.getBkpCode())) {
            return false;
        }

        // check lane_code
        if (!checkLaneCode(vslvoyList, rateUnit, exImType)) {
            return false;
        }

        if (!checkItemMatch(rateUnit.getRouteId(), blno.getRouteId())) {
            return false;
        }

        // check begin_date to end_date
        return checkValidDate(rateUnit.getBeginDate(), rateUnit.getEndDate(), etuLocaleDate);
    }

    private boolean checkLaneCode(List<BlnoVslvoyDO> vslvoyList, LocalCostRateDO rateUnit, String exImType) {
        if (StringUtils.isEmpty(rateUnit.getLaneCodes())) {
            return true;
        }
        for (BlnoVslvoyDO vslvoy : vslvoyList) {
            if (ExImTypeEnum.IM.getValue().equals(exImType)) {
                if (checkItemsMatch(rateUnit.getLaneCodes(), vslvoy.getLaneCodeIm())) {
                    return true;
                }
            }
            if (ExImTypeEnum.EX.getValue().equals(exImType)) {
                if (checkItemsMatch(rateUnit.getLaneCodes(), vslvoy.getLaneCodeEx())) {
                    return true;
                }
            }
        }
        return false;
    }

    private List<LocalCostRateDO> getSortCntrLocalCostRate(List<LocalCostRateDO> rateUnitList) {
        List<LocalCostRateDO> rateCntrUnitList = rateUnitList.stream()
            .filter(rate -> rate.getPer().equals(CalculatePerEnum.CNTR.getValue())).collect(Collectors.toList());
        List<LocalCostRateDO> sortedRateList = rateCntrUnitList.stream()
            .sorted(Comparator
                .nullsFirst(Comparator.comparing(LocalCostRateDO::getChargeCode,
                    Comparator.nullsFirst(String::compareTo).reversed()))
                .thenComparing(Comparator.nullsFirst(Comparator.comparing(LocalCostRateDO::getCntrSize,
                    Comparator.nullsFirst(String::compareTo).reversed())))
                .thenComparing(Comparator.nullsFirst(Comparator.comparing(LocalCostRateDO::getCntrType,
                    Comparator.nullsFirst(String::compareTo).reversed())))
                .thenComparing(Comparator.nullsFirst(Comparator.comparing(LocalCostRateDO::getCntrStatus,
                    Comparator.nullsFirst(String::compareTo).reversed())))
                .thenComparing(Comparator.nullsFirst(Comparator.comparing(LocalCostRateDO::getCargoType,
                    Comparator.nullsFirst(String::compareTo).reversed())))
                .thenComparing(Comparator.nullsFirst(Comparator.comparing(LocalCostRateDO::getLaneCodes,
                    Comparator.nullsFirst(String::compareTo).reversed())))
                .thenComparing(Comparator.nullsFirst(Comparator.comparing(LocalCostRateDO::getBkpCodes,
                    Comparator.nullsFirst(String::compareTo).reversed())))
                .thenComparing(Comparator.nullsFirst(Comparator.comparing(LocalCostRateDO::getVesselCode,
                    Comparator.nullsFirst(String::compareTo).reversed()))))
            .collect(Collectors.toList());
        return sortedRateList;
    }

    private String getVesselCode(List<BlnoVslvoyDO> vslvoyList, String exImType, String currentPort) {
        if (ExImTypeEnum.IM.getValue().equals(exImType)) {
            for (BlnoVslvoyDO vslvoy : vslvoyList) {
                if (vslvoy.getPodCode().equals(currentPort)) {
                    return vslvoy.getVesselCode();
                }
            }
        } else {
            for (BlnoVslvoyDO vslvoy : vslvoyList) {
                if (vslvoy.getPolCode().equals(currentPort)) {
                    return vslvoy.getVesselCode();
                }
            }
        }
        return null;
    }

    private String getDepotCode(List<BlnoVslvoyDO> vslvoyList, String exImType, String currentPort, String transferCode,
        String terminalCode) {
        if (ExImTypeEnum.IM.getValue().equals(exImType)) {
            return getDepotCodeIm(vslvoyList, currentPort, transferCode, terminalCode);
        } else {
            return getDepotCodeEx(vslvoyList, currentPort, terminalCode);
        }
    }

    /**
     * 进口堆场
     *
     * @param vslvoyList 船名航次
     * @param currentPort portCD
     * @return java.lang.String
     */
    private String getDepotCodeIm(List<BlnoVslvoyDO> vslvoyList, String currentPort, String transferCode,
        String terminalCode) {
        if (!StringUtils.isEmpty(transferCode)) {
            return transferCode;
        }

        // currentPort=="CNNGB"时，获取第一条航线polCode
        // polCode==currentPort时，返回该船pod_terminal；否则返回null
        if (PORT_CODE_CNNGB.equals(currentPort)) {
            boolean exist = vslvoyList.stream().anyMatch(item -> item.getPodCode().equals(currentPort));
            if (exist) {
                return terminalCode;
            } else {
                return null;
            }
        } else {
            // currentPort!="CNNGB"时，获取最后一条船podCode
            // podCode==currentPort时，返回该船stuff_loc_im；否则返回null
            Optional<BlnoVslvoyDO> vslvoy =
                vslvoyList.stream().filter(item -> item.getPodCode().equals(currentPort)).findFirst();
            return vslvoy.map(BlnoVslvoyDO::getStuffLocIm).orElse(null);
        }
    }

    /**
     * 出口堆场
     *
     * @param vslvoyList 船名航次
     * @param currentPort portCD
     * @return java.lang.String
     */
    private String getDepotCodeEx(List<BlnoVslvoyDO> vslvoyList, String currentPort, String terminalCode) {
        // currentPort=="CNNGB"时，获取所有polCode
        // 存在polCode==currentPort时，返回该船pol_terminal；否则返回null
        if (PORT_CODE_CNNGB.equals(currentPort)) {
            boolean exist = vslvoyList.stream().anyMatch(item -> item.getPolCode().equals(currentPort));
            if (exist) {
                return terminalCode;
            } else {
                return null;
            }
        } else {
            // currentPort!="CNNGB"时，获取第一条船polCode
            // podCode==currentPort时，返回该船stuff_loc_ex；否则返回null
            Optional<BlnoVslvoyDO> vslvoy =
                vslvoyList.stream().filter(item -> item.getPolCode().equals(currentPort)).findFirst();
            return vslvoy.map(BlnoVslvoyDO::getStuffLocEx).orElse(null);
        }
    }

    private void saveCalLog(BlnoDO blnoDO, List<LocalCostRateDO> rateUnitList, List<BlnoLocalCostDO> blnoLocalHostList,
        List<BlnoCntrDO> blnoCntrDOList, String calType, String status, String failMessage) {
        CalculateOperateLogCreateReqVO createReqVO = new CalculateOperateLogCreateReqVO();
        createReqVO.setBusinessId(blnoDO.getId());
        createReqVO.setBusinessNo(blnoDO.getBlNo());
        createReqVO.setPort(blnoDO.getCurrentPort());
        createReqVO.setCalculateType(calType);
        createReqVO.setOperationStatus(status);
        createReqVO.setOperationFailDesc(failMessage);
        Gson gson = new Gson();
        String reqJson = gson.toJson(rateUnitList);
        String rpsJson = gson.toJson(blnoLocalHostList);
        String cntrJson = gson.toJson(blnoCntrDOList);
        createReqVO.setReceiveJson(rpsJson);
        createReqVO.setSendJson(reqJson);
        createReqVO.setContainerJson(cntrJson);
        calLogService.createCalculateOperateLog(createReqVO);
    }

    @Override
    public void deleteImDetailByBlnoIdList(Collection<Long> blnoIds) {
        LambdaQueryWrapperX<BlnoLocalCostDetailDO> queryWrapperX = new LambdaQueryWrapperX<>();
        queryWrapperX.in(BlnoLocalCostDetailDO::getBlnoId, blnoIds).eq(BlnoLocalCostDetailDO::getImExType,
            ExImTypeEnum.IM.getValue());
        blnoLocalCostDetailMapper.delete(queryWrapperX);
    }

    @Override
    public BlnoLocalCostImAllRespVO getBlnoAllLocalCost(Long id, String exImType) {
        List<BlnoLocalCostDO> localCostList = getLocalCostByBlnoId(id, exImType);
        List<BlnoLocalCostDetailDO> localCostDetailList = getLocalCostDetailByBlnoId(id, exImType);

        List<BlnoCntrDO> blnoCntrDOList = blnoCntrService.getBlnoCntrByBlnoId(id);

        // 获取供应商信息
        List<BlnoLocalCostImMainRespVO> costRespVOList = BlnoLocalCostImConvert.INSTANCE.convertList(localCostList);
        List<BlnoLocalCostImDetailRespVO> costDetailRespVoList = BlnoLocalCostImConvert.INSTANCE.convertDetailList(localCostDetailList);

        // 过滤null
        Set<String> codes = costRespVOList.stream().map(BlnoLocalCostImMainRespVO::getVendor).filter(Objects::nonNull)
            .filter(name -> !name.isEmpty()).collect(Collectors.toSet());
        Map<String, String> companyNameMap = nameTransferService.getCompanyNameMap(new ArrayList<>(codes));
        costRespVOList.forEach(cost -> cost.setVendorNameEn(companyNameMap.getOrDefault(cost.getVendor(), null)));
        costDetailRespVoList.forEach(cost -> cost.setVendorNameEn(companyNameMap.getOrDefault(cost.getVendor(), null)));

        // 设置箱状态
        List<BlnoCntrRespVO> blnoCntrRespList = BlnoCntrConvert.INSTANCE.convertList(blnoCntrDOList);
        BlnoDO blnoDO = blnoService.getBlno(id);
        blnoCntrRespList.forEach(cntr -> cntr.setCntrStatus(blnoDO.getCntrStatus()));

        BlnoLocalCostImAllRespVO respVO = new BlnoLocalCostImAllRespVO();
        respVO.setLocalCostList(costRespVOList);
        respVO.setLocalCostDetailList(costDetailRespVoList);
        respVO.setContainerInfoList(blnoCntrRespList);

        CalculateOperateLogDO logDO;
        if (ExImTypeEnum.IM.getValue().equals(exImType)) {
            logDO = calLogService.getNewOperateLogByBlnoId(id, CalculateTypeEnum.CALCULATE_LOCAL_COST_IM);
        } else {
            logDO = calLogService.getNewOperateLogByBlnoId(id, CalculateTypeEnum.CALCULATE_LOCAL_COST_EX);
        }
        if (logDO != null) {
            respVO.setSendJson(logDO.getSendJson());
            respVO.setReceiveJson(logDO.getReceiveJson());
        }
        return respVO;
    }

    private List<BlnoLocalCostDetailDO> getLocalCostDetailByBlnoId(Long blnoId, String exImType) {
        LambdaQueryWrapperX<BlnoLocalCostDetailDO> queryWrapperX = new LambdaQueryWrapperX<>();
        queryWrapperX.eq(BlnoLocalCostDetailDO::getBlnoId, blnoId).eqIfPresent(BlnoLocalCostDetailDO::getImExType,
            exImType);
        queryWrapperX.orderByAsc(BlnoLocalCostDetailDO::getId);
        return blnoLocalCostDetailMapper.selectList(queryWrapperX);
    }

    @Override
    public List<BlnoLocalCostDO> getLocalCostByBlnoId(Long blnoId, String exImType) {
        LambdaQueryWrapperX<BlnoLocalCostDO> queryWrapperX = new LambdaQueryWrapperX<>();
        queryWrapperX.eq(BlnoLocalCostDO::getBlnoId, blnoId).eqIfPresent(BlnoLocalCostDO::getImExType, exImType);
        queryWrapperX.orderByAsc(BlnoLocalCostDO::getId);
        return blnoLocalCostMapper.selectList(queryWrapperX);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Map<Long, String> toAccountLocalCost(Collection<Long> ids, String exImType) {
        Map<Long, String> errMsgMap = new HashMap<>(16);
        // 过滤无效id
        List<BlnoDO> blnoDoList = getBlnoCheckIds(ids, errMsgMap);
        if (CollectionUtils.isEmpty(blnoDoList)) {
            return errMsgMap;
        }
        ids = blnoDoList.stream().map(BlnoDO::getId).collect(Collectors.toSet());
        // redis加锁
        Set<Long> lockIds = new HashSet<>();
        for (Long blnoId : ids) {
            if (redisUtils.checKeyExist(RedisKeyConstants.LOCAL_COST_KEY + blnoId)) {
                lockIds.add(blnoId);
                errMsgMap.put(blnoId, BlnoFreightConstants.REDIS_KEY_LOCKED);
            } else {
                redisUtils.setStringTimeOutByMinute(RedisKeyConstants.LOCAL_COST_KEY + blnoId, String.valueOf(blnoId),
                    redisTimeOut);
                errMsgMap.put(blnoId, CommonConstants.RESPONSE_SUCCESS);
            }
        }
        if (CollectionUtils.isNotEmpty(lockIds)) {
            ids.removeAll(lockIds);
            blnoDoList =
                blnoDoList.stream().filter(blno -> !lockIds.contains(blno.getId())).collect(Collectors.toList());
        }
        if (CollectionUtils.isEmpty(ids)) {
            log.info(" all ids is lock");
            return errMsgMap;
        }

        LambdaQueryWrapperX<BlnoLocalCostDO> queryWrapperX = new LambdaQueryWrapperX<>();
        queryWrapperX.eq(BlnoLocalCostDO::getToAccount, false).eq(BlnoLocalCostDO::getImExType, exImType);
        List<BlnoLocalCostDO> blnoLocalCostDOList = blnoLocalCostMapper.selectList(queryWrapperX);
        if (CollectionUtils.isEmpty(blnoLocalCostDOList)) {
            log.info("no freight need to send to account");
            return errMsgMap;
        }
        try {
            List<BlnoVslvoyDO> blnoVslvoyDOList = blnoVslvoyService.getBlnoVslvoyBySiIdList(ids);
            DocBlnoBaseReqDTO docBlnoBaseReqDTO =
                buildDocBlnoBaseReqDTO(blnoLocalCostDOList, blnoDoList, blnoVslvoyDOList);
            boolean success = accountTransfer.createBlnoPay(docBlnoBaseReqDTO, PayTypeEnum.COST.getValue());
            if (!success) {
                throw ServiceExceptionUtil
                    .exception(BlnoLocalCostImErrorCodeConstants.LOCAL_COST_IM_SEND_ACCOUNT_FAILED);
            }
            blnoLocalCostDOList.forEach(freight -> freight.setToAccount(true));
            blnoLocalCostMapper.updateBatch(blnoLocalCostDOList);
        } finally {
            for (Long blnoId : ids) {
                redisUtils.deleteString(RedisKeyConstants.LOCAL_COST_KEY + blnoId);
            }
        }
        return errMsgMap;
    }

    @Override
    public void createBlnoLocalCost(BlnoLocalCostImCreateReqVO createReqVO) {
        BlnoLocalCostDO blnoLocalCostDO = BlnoLocalCostImConvert.INSTANCE.convert(createReqVO);
        blnoLocalCostDO.setRecordType(RecordTypeEnum.MANUAL.getValue());
        blnoLocalCostDO.setOriginalId(null);
        blnoLocalCostDO.setToAccount(false);
        blnoLocalCostDO.setPayment(PaymentEnum.C.getValue());
        blnoLocalCostDO.setImExType(ExImTypeEnum.IM.getValue());
        blnoLocalCostMapper.insert(blnoLocalCostDO);
    }

    @Override
    public void updateBlnoLocalCost(BlnoLocalCostImUpdateReqVO updateReqVO) {
        BlnoLocalCostDO blnoLocalCostDO = BlnoLocalCostImConvert.INSTANCE.convert(updateReqVO);
        validOperateAllow(updateReqVO.getId());
        blnoLocalCostDO.setRecordType(RecordTypeEnum.MANUAL.getValue());
        blnoLocalCostDO.setOriginalId(null);
        blnoLocalCostDO.setToAccount(false);
        blnoLocalCostDO.setPayment(PaymentEnum.C.getValue());
        blnoLocalCostDO.setImExType(ExImTypeEnum.IM.getValue());
        blnoLocalCostMapper.updateById(blnoLocalCostDO);
    }

    private void validOperateAllow(Long id) {
        BlnoLocalCostDO blnoLocalCostDO = blnoLocalCostMapper.selectById(id);
        if (blnoLocalCostDO == null) {
            throw ServiceExceptionUtil.exception(BlnoLocalCostImErrorCodeConstants.BLNO_LOCAL_COST_NOT_EXISTS);
        }

        if (RecordTypeEnum.AUTO.getValue().equals(blnoLocalCostDO.getRecordType()) || blnoLocalCostDO.getToAccount()) {
            throw ServiceExceptionUtil.exception(BlnoLocalCostImErrorCodeConstants.LOCAL_COST_IM_OPERATE_NOT_ALLOWED);
        }
    }

    @Override
    public void deleteBlnoLocalCost(Long id) {
        validOperateAllow(id);
        blnoLocalCostMapper.deleteById(id);
    }

    @Override
    public void copyFromIncome(Long id) {
        LambdaQueryWrapperX<BlnoLocalChargeDO> queryWrapperX = new LambdaQueryWrapperX<>();
        queryWrapperX.eq(BlnoLocalChargeDO::getId, id).eq(BlnoLocalChargeDO::getImExType, ExImTypeEnum.IM.getValue());
        List<BlnoLocalChargeDO> localChargeList = localChargeMapper.selectList(queryWrapperX);
        // 过滤红冲数据
        localChargeList = getLocalChargeAccountFilter(localChargeList);
        if (CollectionUtils.isEmpty(localChargeList)) {
            throw ServiceExceptionUtil.exception(BlnoLocalCostImErrorCodeConstants.NO_LOCAL_CHARGE_COPY);
        }
        List<BlnoLocalCostDO> blnoLocalCostList = new ArrayList<>();
        for (BlnoLocalChargeDO localCharge : localChargeList) {
            BlnoLocalCostDO localCost = BlnoLocalCostImConvert.INSTANCE.convert(localCharge);
            localCost.setRecordType(RecordTypeEnum.MANUAL.getValue());
            localCost.setOriginalId(null);
            localCost.setToAccount(false);
            localCost.setPayment(PaymentEnum.C.getValue());
            localCost.setImExType(ExImTypeEnum.IM.getValue());
            localCost.setId(null);
        }
        blnoLocalCostMapper.insertBatch(blnoLocalCostList);
    }

    private DocBlnoBaseReqDTO buildDocBlnoBaseReqDTO(List<BlnoLocalCostDO> blnoLocalCostDOList, List<BlnoDO> blnoDOList,
        List<BlnoVslvoyDO> blnoVslvoyDOList) {
        Map<Long, List<BlnoVslvoyDO>> vslvoyListMap =
            blnoVslvoyDOList.stream().collect(Collectors.groupingBy(BlnoVslvoyDO::getBlnoId));
        Map<Long, List<BlnoLocalCostDO>> localCostListMap =
            blnoLocalCostDOList.stream().collect(Collectors.groupingBy(BlnoLocalCostDO::getBlnoId));
        DocBlnoBaseReqDTO docBlnoBaseReq = new DocBlnoBaseReqDTO();
        List<DocBlnoCreateReqDTO> docBlnoList = new ArrayList<>();
        blnoDOList.forEach(blnoDO -> {
            DocBlnoCreateReqDTO blnoReq = BlnoFreightConvert.INSTANCE.convertAccountReq(blnoDO);
            blnoReq.setSender(String.valueOf(SecurityFrameworkUtils.getLoginUserId()));
            LocalDateTime now = LocalDateTime.now();
            DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
            String formattedString = now.format(formatter);
            blnoReq.setSendTime(formattedString);
            List<DocBlnoVslvoyReqDTO> docBlnoVslvoyList =
                BlnoFreightServiceImpl.buildVslvoyReq(vslvoyListMap.getOrDefault(blnoDO.getId(), new ArrayList<>()));
            String localPort = VslvoyUtil.getLastPort(vslvoyListMap.getOrDefault(blnoDO.getId(), new ArrayList<>()));
            List<DocBlnoFreightInfoReqDTO> freightInfoList =
                buildFreightReq(localCostListMap.getOrDefault(blnoDO.getId(), new ArrayList<>()), localPort);
            blnoReq.setDocBlnoVslvoyList(docBlnoVslvoyList);
            blnoReq.setFreightInfoList(freightInfoList);
            blnoReq.setPayType(PayTypeEnum.COST.getValue());
            docBlnoList.add(blnoReq);
        });
        docBlnoBaseReq.setDocBlnoList(docBlnoList);
        return docBlnoBaseReq;
    }

    private List<DocBlnoFreightInfoReqDTO> buildFreightReq(List<BlnoLocalCostDO> blnoLocalCostList, String localPort) {
        List<DocBlnoFreightInfoReqDTO> vslvoyReqList = new ArrayList<>();
        blnoLocalCostList.forEach(freight -> {
            DocBlnoFreightInfoReqDTO freightReq = BlnoLocalCostImConvert.INSTANCE.convertLocalCostReq(freight);
            freightReq.setLocalPortCode(localPort);
            vslvoyReqList.add(freightReq);
        });
        return vslvoyReqList;
    }

    private void saveLocalCostDb(Collection<Long> ids, List<BlnoLocalCostDO> blnoLocalCostList,
        List<BlnoLocalCostDetailDO> blnoLocalCostDetailList, String exImType) {
        if (!CollectionUtils.isEmpty(blnoLocalCostDetailList)) {
            deleteImDetailByBlnoIdList(ids);
            blnoLocalCostDetailMapper.insertBatch(blnoLocalCostDetailList);
        }

        if (!CollectionUtils.isEmpty(blnoLocalCostList)) {
            // 删除未发送业财的DO对象过滤，并同时清除其导致的红冲标记
            deleteNotSendAccount(ids, exImType);

            // 获取已发送业财的DO对象，过滤已经红冲的数据
            Map<String, BlnoLocalCostDO> localCostAccountMap = getLocalCostAccountFilterMap(ids, exImType);

            // 待保存数据库中的数据表
            List<BlnoLocalCostDO> localCostDbList = new ArrayList<>();
            Set<String> existKey = new HashSet<>();
            // 生成db对象，时间复杂度O(n+m), 空间复杂度T(n+m)
            for (BlnoLocalCostDO localCost : blnoLocalCostList) {
                String key = buildKey(localCost);
                // 存在同一条费用记录
                if (localCostAccountMap.containsKey(key)) {
                    existKey.add(key);
                    BlnoLocalCostDO localCostAccount = localCostAccountMap.get(key);
                    // 费率不同，生成红冲记录
                    if (localCost.getAmount().compareTo(localCostAccount.getAmount()) != 0
                        || !localCost.getPer().equals(localCostAccount.getPer())) {
                        BlnoLocalCostDO localCostTmp = buildDashedBackLocalCost(localCostAccount);
                        localCostDbList.add(localCostTmp);
                        localCostDbList.add(localCost);
                        localCostAccount.setRedStatus(RedStatusEnum.RED.getValue());
                        localCostDbList.add(localCostAccount);
                    }
                } else {
                    // 发送财务数据表不存在该记录，保存至数据表
                    localCostDbList.add(localCost);
                }
            }

            // 处理已经发送给财务，但是此次计算没有的数据，生成红冲记录
            for (Map.Entry<String, BlnoLocalCostDO> entry : localCostAccountMap.entrySet()) {
                String key = entry.getKey();
                if (existKey.contains(key)) {
                    continue;
                }
                BlnoLocalCostDO localCostAccount = entry.getValue();
                localCostAccount.setRedStatus(RedStatusEnum.RED.getValue());
                BlnoLocalCostDO localCostTmp = buildDashedBackLocalCost(localCostAccount);
                localCostDbList.add(localCostTmp);
                localCostDbList.add(localCostAccount);
            }

            blnoLocalCostMapper.saveOrUpdateBatch(localCostDbList);
        }
    }

    private void deleteNotSendAccount(Collection<Long> blnoIds, String exImType) {
        LambdaQueryWrapperX<BlnoLocalCostDO> deleteWrapperX = new LambdaQueryWrapperX<>();
        deleteWrapperX.eq(BlnoLocalCostDO::getImExType, exImType)
            .eq(BlnoLocalCostDO::getRecordType, RecordTypeEnum.AUTO.getValue()).eq(BlnoLocalCostDO::getToAccount, false)
            .in(BlnoLocalCostDO::getBlnoId, blnoIds);
        List<BlnoLocalCostDO> deleteLocalCostList = blnoLocalCostMapper.selectList(deleteWrapperX);
        // 清除未发送财务的数据导致的红冲标记
        Set<Long> ids = deleteLocalCostList.stream().map(BlnoLocalCostDO::getOriginalId).filter(Objects::nonNull)
            .collect(Collectors.toSet());
        if (!CollectionUtils.isEmpty(ids)) {
            LambdaUpdateWrapper<BlnoLocalCostDO> updateWrapperX = new LambdaUpdateWrapper<>();
            updateWrapperX.set(BlnoLocalCostDO::getRedStatus, RedStatusEnum.NOTRED.getValue())
                .in(BlnoLocalCostDO::getId, ids);
            blnoLocalCostMapper.update(null, updateWrapperX);
        }
        blnoLocalCostMapper.delete(deleteWrapperX);
    }

    private List<BlnoLocalChargeDO> getLocalChargeAccountFilter(List<BlnoLocalChargeDO> localChargeList) {
        if (CollectionUtils.isEmpty(localChargeList)) {
            return new ArrayList<>();
        }
        Set<Long> originalIds = localChargeList.stream().map(BlnoLocalChargeDO::getOriginalId).filter(Objects::nonNull)
            .collect(Collectors.toSet());
        return localChargeList.stream()
            .filter(item -> !originalIds.contains(item.getId()) && item.getOriginalId() == null)
            .collect(Collectors.toList());
    }

    private Map<String, BlnoLocalCostDO> getLocalCostAccountFilterMap(Collection<Long> ids, String exImType) {
        List<BlnoLocalCostDO> localCostAccountList = getLocalCostByBlnoIdList(ids, exImType);
        Set<Long> originalIds = localCostAccountList.stream().map(BlnoLocalCostDO::getOriginalId)
            .filter(Objects::nonNull).collect(Collectors.toSet());
        return localCostAccountList.stream()
            .filter(item -> !originalIds.contains(item.getId()) && item.getOriginalId() == null)
            .collect(Collectors.toMap(this::buildKey, freight -> freight, (existingValue, newValue) -> existingValue));
    }

    private List<BlnoLocalCostDO> getLocalCostByBlnoIdList(Collection<Long> ids, String exImType) {
        LambdaQueryWrapperX<BlnoLocalCostDO> queryWrapperX = new LambdaQueryWrapperX<>();
        queryWrapperX.eq(BlnoLocalCostDO::getImExType, exImType)
            .eq(BlnoLocalCostDO::getRecordType, RecordTypeEnum.AUTO.getValue()).in(BlnoLocalCostDO::getBlnoId, ids);
        return blnoLocalCostMapper.selectList(queryWrapperX);
    }

    private String buildKey(BlnoLocalCostDO item) {
        StringBuilder sb = new StringBuilder();
        if (item.getCurrency() != null) {
            sb.append(item.getCurrency());
        }

        if (item.getBlnoId() != null) {
            sb.append(item.getBlnoId());
        }

        if (item.getChargeCode() != null) {
            sb.append(item.getChargeCode());
        }

        if (item.getCargoType() != null) {
            sb.append(item.getCargoType());
        }

        if (item.getCntrType() != null) {
            sb.append(item.getCntrType());
        }

        if (item.getCntrSize() != null) {
            sb.append(item.getCntrSize());
        }

        if (item.getQuantity() != null) {
            sb.append(item.getQuantity());
        }

        if (item.getPayment() != null) {
            sb.append(item.getPayment());
        }

        if (item.getImExType() != null) {
            sb.append(item.getImExType());
        }

        return sb.toString();
    }

    private BlnoLocalCostDO buildDashedBackLocalCost(BlnoLocalCostDO localCostAccount) {
        BlnoLocalCostDO localCostTmp = BlnoLocalCostImConvert.INSTANCE.convertLocalCostDO(localCostAccount);
        localCostTmp.setToAccount(false);
        localCostTmp.setRate(localCostAccount.getRate().negate());
        localCostTmp.setAmount(localCostAccount.getAmount().negate());
        localCostTmp.setOriginalId(localCostAccount.getId());
        localCostTmp.setId(null);
        localCostTmp.setRedStatus(RedStatusEnum.RED.getValue());
        return localCostTmp;
    }

    private List<LocalCostRateDO> getLocalCostRateList(String exImType, String currentPort) {
        LambdaQueryWrapperX<LocalCostRateDO> queryWrapperX = new LambdaQueryWrapperX<>();
        queryWrapperX.eq(LocalCostRateDO::getAuditFlag, true).eqIfPresent(LocalCostRateDO::getImExType, exImType)
            .eqIfPresent(LocalCostRateDO::getPortCode, currentPort);
        return localCostRateMapper.selectList(queryWrapperX);
    }

    private boolean checkItemMatch(String itemSrc, String itemDst) {
        if (StringUtils.isEmpty(itemSrc)) {
            return true;
        }

        if (StringUtils.isEmpty(itemDst)) {
            return false;
        }

        return itemSrc.equalsIgnoreCase(itemDst);
    }

    private boolean checkItemMatch(Long itemSrc, Long itemDst) {
        if (itemSrc == null) {
            return true;
        }

        if (itemDst == null) {
            return false;
        }

        return itemSrc.equals(itemDst);
    }

    private boolean checkItemsMatch(String itemsSrc, String itemDst) {
        if (StringUtils.isEmpty(itemsSrc)) {
            return true;
        }

        if (StringUtils.isEmpty(itemDst)) {
            return false;
        }

        String[] parts = itemsSrc.split(SPILT_RATE_UNIT);
        for (String part : parts) {
            if (itemDst.equals(part)) {
                return true;
            }
        }

        return false;
    }

    private LocalDate getBusinessDate(String currentPort, String voyAge, String laneCode, List<String> vesselVoys) {
        VesselVoyRespDTO respDto = costChargeUtil.getVoyQueryRespDTO(currentPort, voyAge, laneCode, vesselVoys);
        String ptu = respDto.getPtu();
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
        return LocalDate.parse(ptu, formatter);
    }

    private boolean checkValidDate(LocalDate beginDate, LocalDate endDate, LocalDate etuLocalDate) {
        return beginDate.isBefore(etuLocalDate) && endDate.isAfter(etuLocalDate);
    }

}
